﻿using System;
using System.Drawing;
using System.Windows.Forms;
using static System.Windows.Forms.ListView;

namespace ICSharpCode.WinFormsUI.Controls
{
    public class NListView : Panel
    {
        private bool uihooked = false;
        private int totalWidth = 0;
        private int totalHeight = 0;

        private string _lastToolTipText = "";
        private Cursor _lastCursor = Cursors.Default;

        ToolTip toolTip = new ToolTip();
        Color headerBackColor = Color.FromArgb(078, 078, 078);
        Color mouseHoverBackColor = Color.FromArgb(035, 035, 035);
                
        private bool drawDragLine = false;
        private Point[] dragLine = new Point[2];
        private bool mouseInDragRectangle = false;

        private string _themeName;
        public string ThemeName
        {
            get { return _themeName; }             
        }

        private int _columnHeaderHeight = 30;
        public int ColumnHeaderHeight
        {
            get
            {
                return _columnHeaderHeight;
            }
            set
            {
                _columnHeaderHeight = value;
                OptionsChanged();
            }
        }

        private int _itemHeight = 28;
        public int ItemHeight
        {
            get { return _itemHeight; }
            set
            {
                _itemHeight = value;
                OptionsChanged();
            }
        }

        private View _view;
        public View View
        {
            get { return _view; }
            set
            {
                _view = value;
                OptionsChanged();
            }
        }

        private bool _gridLines;
        public bool GridLines
        {
            get { return _gridLines; }
            set
            {
                _gridLines = value;
                OptionsChanged();
            }
        }

        private bool _multiSelect;
        public bool MultiSelect
        {
            get { return _multiSelect; }
            set { _multiSelect = value; }
        }

        private bool _labelEdit;
        public bool LabelEdit
        {
            get { return _labelEdit; }
            set { _labelEdit = value; }
        }

        private NColumnHeaderCollection _columns;
        public NColumnHeaderCollection Columns
        {
            get { return _columns; }
            set
            {
                Columns = value;
                OptionsChanged();
            }
        }

        private NListViewItemCollection _items;
        public NListViewItemCollection Items
        {
            get { return _items; }
            set
            {
                _items = value;
                OptionsChanged();
            }
        }

        private NListViewGroupCollection _groups;
        public NListViewGroupCollection Groups
        {
            get { return _groups; }
            set
            {
                _groups = value;
                OptionsChanged();
            }
        }

        private bool _useCompatibleStateImageBehavior;
        public bool UseCompatibleStateImageBehavior
        {
            get { return _useCompatibleStateImageBehavior; }
            set { _useCompatibleStateImageBehavior = value; }
        }

        private ImageList _largeImageList;
        public ImageList LargeImageList
        {
            get { return _largeImageList; }
            set { _largeImageList = value; }
        }
        
        private ImageList _stateImageList;
        public ImageList StateImageList
        {
            get { return _stateImageList; }
            set { _stateImageList = value; }
        }

        private Point VisiableTop = new Point(0, 0);

        private NHScrollBar hScrollBar;
        private NVScrollBar vScrollBar;

        private bool VerticalScrollBarVisible
        {
            get
            {
                totalHeight = ColumnHeaderHeight;
                bool _verticalScrollBarVisible = false;               
                for (int i = 0; i < Items.Count; i++)
                {
                    totalHeight = totalHeight + ItemHeight;
                }
                if (totalHeight > this.Height)
                {
                    _verticalScrollBarVisible = true;
                }
                return _verticalScrollBarVisible;
            }
        }

        private bool HorizontalScrollBarVisible
        {
            get
            {
                totalWidth = 0;
                bool horizontalScrollBarVisible = false;                
                for (int i = 0; i < Columns.Count; i++)
                {
                    totalWidth = totalWidth + Columns[i].Width;
                }
                if (totalWidth > (VerticalScrollBarVisible ? this.Width - ScrollBarInformation.VerticalScrollBarWidth : this.Width))
                {
                    horizontalScrollBarVisible = true;
                }
                return horizontalScrollBarVisible;
            }
        }
        
        public event EventHandler SelectedIndexChanged;
 
        public NListView()
        {
            _items = new NListViewItemCollection(this);
            _groups = new NListViewGroupCollection(this);
            _columns = new NColumnHeaderCollection(this);
            InitializeControl();
        }

        public void BeginUpdate()
        {
            uihooked = true;
        }

        public void EndUpdate()
        {
            uihooked = false;
        }

        private void OptionsChanged()
        {
            if (!uihooked)
                this.Invalidate();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Graphics dc = e.Graphics;
            switch (_view)
            {
                case View.Details:
                    DrawDetail(dc);
                    break;
                case View.List:
                    DrawList(dc);
                    break;
            }
            DrawDragLine(dc);
            DrawBorder(dc);
        }

        private void DrawDragLine(Graphics g)
        {
            if (drawDragLine)
                g.DrawLine(new Pen(Color.Red), dragLine[0], dragLine[1]);
        }

        private void DrawBorder(Graphics g)
        {           
            System.Windows.Forms.ControlPaint.DrawBorder(g, this.ClientRectangle, SystemColors.ControlDark, ButtonBorderStyle.Solid);
        }

        #region DrawDetail

        private void DrawColumnHeader(Graphics g)
        {
            int left = VisiableTop.X;            
            for (int i = 0; i < Columns.Count; i++)
            {
                var columnHeader = Columns[i];
                Rectangle bound = new Rectangle(left, 1, columnHeader.Width, ColumnHeaderHeight);
                if (columnHeader.MouseInside)
                    g.FillRectangle(new SolidBrush(mouseHoverBackColor), bound);
                else
                    g.FillRectangle(new SolidBrush(headerBackColor), bound);
                g.DrawLine(new Pen(SystemColors.ControlDark), new Point(left + columnHeader.Width - 1, 1), new Point(left + columnHeader.Width - 1, ColumnHeaderHeight));
                bound.Inflate(-4, -4);
                string headerText = columnHeader.Text;
                if(!string.IsNullOrEmpty(headerText)&& headerText=="Column")
                {
                    headerText = "Column" + columnHeader.Index.ToString();
                }
                TextRenderer.DrawText(g, headerText, this.Font, bound, this.ForeColor, TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.WordEllipsis);                
                columnHeader.Bound = new Rectangle(left, 1, columnHeader.Width, ColumnHeaderHeight);
                left = left + columnHeader.Width;
            }
            g.DrawLine(new Pen(SystemColors.ControlDark), new Point(VisiableTop.X, ColumnHeaderHeight), new Point(left - 1, ColumnHeaderHeight));
        }

        private void DrawRows(Graphics g)
        {
            int top = VisiableTop.Y + ColumnHeaderHeight;
            bool drawIcon = this.StateImageList != null;
            for (int i = 0; i < Items.Count; i++)
            {
                int left = VisiableTop.X;
                NListViewItem item = Items[i];
                item.Bound = new Rectangle(left, top, totalWidth, ItemHeight);
                for (int j = 0; j < Columns.Count; j++)
                {
                    NColumnHeader header = Columns[j];
                    Rectangle bound = new Rectangle(left, top, header.Width, ItemHeight);
                    if (item.MouseInside)
                        g.FillRectangle(new SolidBrush(mouseHoverBackColor), bound);
                    else
                        g.FillRectangle(new SolidBrush(BackColor), bound);

                    if (j == 0 && drawIcon && item.StateImageIndex >= 0)
                    {
                        Image icon = this.StateImageList.Images[item.StateImageIndex];
                        if (icon != null)
                        {
                            g.DrawImage(icon, new Rectangle(left + 4, top + 4, 24, 24),
                                new Rectangle(0, 0, 24, 24), GraphicsUnit.Pixel);
                            bound = new Rectangle(left + 28, top, header.Width - 28, ItemHeight);
                        }
                    }

                    bound.Inflate(-4, -4);
                    if (j == 0)
                        TextRenderer.DrawText(g, item.Text, this.Font, bound, this.ForeColor, TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.WordEllipsis);
                    else
                        TextRenderer.DrawText(g, item.SubItems[j - 1].Text, this.Font, bound, this.ForeColor, TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.WordEllipsis);
                    g.DrawLine(new Pen(SystemColors.ControlDark), new Point(left + header.Width - 1, top), new Point(left + header.Width - 1, top + ItemHeight - 1));                    
                    left = left + header.Width;
                }                
                top = top + ItemHeight;
                g.DrawLine(new Pen(SystemColors.ControlDark), new Point(VisiableTop.X, top - 1), new Point(left - 1, top - 1));
            }
        }

        private void DrawDetail(Graphics g)
        {            
            DrawRows(g);
            DrawColumnHeader(g);
        }

        #endregion

        private void DrawList(Graphics g)
        {

        }

        private void InitializeControl()
        {
            hScrollBar = new NHScrollBar();
            vScrollBar = new NVScrollBar();

            vScrollBar.DisabledProcessDialogKey = true;
            hScrollBar.DisabledProcessDialogKey = true;
            
            vScrollBar.ValueChanged += _vScrollBarEx_ValueChanged;
            hScrollBar.ValueChanged += _hScrollBarEx_ValueChanged;

            Controls.Add(hScrollBar);
            Controls.Add(vScrollBar);

            BackColor = System.Drawing.SystemColors.Window;

            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);

            UpdateScrollBarInformation();
        }
        
        internal void UpdateScrollBarInformation()
        {
            int borderWidth = 1;

            Rectangle rect = new Rectangle(1, 1, Width - 2, Height - 2);

            if (VerticalScrollBarVisible)
            {
                vScrollBar.Visible = true;

                vScrollBar.SmallChange = ItemHeight;
                vScrollBar.LargeChange = rect.Height; 
                
                vScrollBar.Maximum = (int)totalHeight > rect.Height ? (int)totalHeight - rect.Height : rect.Height;

                vScrollBar.Width = ScrollBarInformation.VerticalScrollBarWidth;
                vScrollBar.Height = hScrollBar.Visible ? this.Height - this.hScrollBar.Height - 2 * borderWidth + 1 :
                 this.Height - 2 * borderWidth;
                vScrollBar.Location = new Point(this.Width - vScrollBar.Width - borderWidth, borderWidth);
            }
            else
            {
                vScrollBar.Visible = false;
            }

            if (HorizontalScrollBarVisible)
            {
                hScrollBar.Visible = true;

                hScrollBar.SmallChange = 100;
                hScrollBar.LargeChange = rect.Width; 

                hScrollBar.Maximum = (int)totalWidth > rect.Height ? (int)totalWidth - rect.Height : rect.Height;
                
                hScrollBar.Width = vScrollBar.Visible ? this.Width - this.vScrollBar.Width - 2 * borderWidth + 1 :
                  this.Width - 2 * borderWidth;
                hScrollBar.Height = ScrollBarInformation.HorizontalScrollBarHeight;
                hScrollBar.Location = new Point(borderWidth, this.Height - hScrollBar.Height - borderWidth);

            }
            else
            {
                hScrollBar.Visible = false;
            }
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            UpdateScrollBarInformation();
        }

        private void _vScrollBarEx_ValueChanged(object sender, EventArgs e)
        {
            //this.VisiableTop.Y = -(int)((vScrollBar.Value * 1.0 / vScrollBar.Maximum) * (totalHeight + ItemHeight - this.Height));
            this.VisiableTop = new Point(VisiableTop.X, -vScrollBar.Value);
            this.Invalidate();
        }

        private void _hScrollBarEx_ValueChanged(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        private void OnVScroll(object sender, ScrollEventArgs e)
        {
            int m_vScroll_pos = VisiableTop.Y - (e.NewValue - e.OldValue);
            VisiableTop = new Point(VisiableTop.X, m_vScroll_pos);
            this.Invalidate();
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            base.OnMouseWheel(e);

            if (!this.vScrollBar.Visible)
                return;

            int num1 = -e.Delta / NativeMethods.WHEEL_DELTA * this.ItemHeight;

            int oldValue = this.vScrollBar.Value;
            int newValue = this.vScrollBar.Value + num1;

            if (newValue < this.vScrollBar.Minimum)
                newValue = this.vScrollBar.Minimum;

            if (newValue > this.vScrollBar.Maximum)
                newValue = this.vScrollBar.Maximum;

            OnVScroll(this, new ScrollEventArgs(ScrollEventType.SmallDecrement, oldValue, newValue));

            this.vScrollBar.Value = newValue;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (mouseInDragRectangle)
                drawDragLine = true;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            Point pos = e.Location;
            string toolText = "";
            mouseInDragRectangle = false;            

            Rectangle bound = new Rectangle(0, 0, totalWidth > Width ? Width : totalWidth, totalHeight > Height ? Height : totalHeight);
            if (pos.Y <= ColumnHeaderHeight + ItemHeight)
            {
                foreach (NColumnHeader header in Columns)
                {                   
                    if (header.Bound.Contains(pos))
                    {
                        header.MouseInside = true;
                    }
                    else
                    {
                        header.MouseInside = false;
                    }

                    Rectangle dragRange = new Rectangle(header.Bound.Right-2, 1,4, ColumnHeaderHeight);

                    if (drawDragLine && header.DragSize)
                    {
                        int offsetX = pos.X - header.Bound.Right;
                        header.Width = header.Width + offsetX;

                        dragLine[0] = new Point(pos.X, 0);
                        dragLine[1] = new Point(pos.X, Height);
                        this.Invalidate();

                        return;
                    }

                    header.DragSize = false;

                    if (dragRange.Contains(pos))
                    {                        
                        header.DragSize = true;
                        mouseInDragRectangle = true;
                    }
                    
                }                

                this.Invalidate(new Rectangle(0, 0, Width, ColumnHeaderHeight));
            }
            if (pos.Y > ColumnHeaderHeight)
            {
                int left = VisiableTop.X;
                foreach (NListViewItem item in Items)
                {
                    if (item.Bound.Contains(pos))
                    {
                        toolText = item.Text;
                        item.MouseInside = true;
                        if (bound.Contains(pos))
                        {                            
                            for (int colIndex = 0; colIndex < Columns.Count; colIndex++)
                            {
                                Rectangle cellBound = new Rectangle(left, item.Bound.Y, Columns[colIndex].Width, ItemHeight);
                                if (cellBound.Contains(pos))
                                {
                                    if (colIndex > 0)
                                        toolText = item.SubItems[colIndex - 1].Text;
                                    break;
                                }
                                left = left + Columns[colIndex].Width;
                            }
                        }

                    }
                    else
                    {
                        item.MouseInside = false;
                    }
                }
                this.Invalidate(new Rectangle(0, ColumnHeaderHeight, Width, Height - ColumnHeaderHeight));

                if (_lastToolTipText != toolText)
                {
                    toolTip.SetToolTip(this, toolText);
                }

                _lastToolTipText = toolText;

            }

            if (mouseInDragRectangle)
            {
                if (this.Cursor != Cursors.SizeWE)
                    this.Cursor = Cursors.SizeWE;               
            }
            else
            {
                if (this.Cursor != Cursors.Default)
                    this.Cursor = Cursors.Default;
            }                

            //this.Invalidate(bound);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            if (drawDragLine)
            {
                this.Invalidate();
            }
            drawDragLine = false;
        }

        public void SetTheme(string themeName)
        {
            Color foreColor = SystemColors.WindowText;
            Color backColor = SystemColors.Window;
            _themeName = themeName;
            switch (_themeName)
            {
                case "Default":
                    vScrollBar.BorderColor = SystemColors.ControlLight;
                    hScrollBar.BorderColor = SystemColors.ControlLight;
                    vScrollBar.ScrollBarRenderer = new WhiteScrollBarRenderer();
                    hScrollBar.ScrollBarRenderer = new WhiteScrollBarRenderer();
                    backColor = SystemColors.Window;
                    foreColor = SystemColors.WindowText;
                    headerBackColor = SystemColors.Control;
                    mouseHoverBackColor = SystemColors.ControlLight;
                    break;
                case "Black":
                    vScrollBar.BorderColor = Color.FromArgb(93, 140, 201);
                    hScrollBar.BorderColor = Color.FromArgb(93, 140, 201);
                    vScrollBar.ScrollBarRenderer = new BlackScrollBarRenderer();
                    hScrollBar.ScrollBarRenderer = new BlackScrollBarRenderer();
                    backColor = Color.FromArgb(045, 045, 048);
                    foreColor = Color.FromArgb(200, 200, 200);
                    headerBackColor = Color.FromArgb(078, 078, 078);
                    mouseHoverBackColor = Color.FromArgb(035, 035, 035);
                    break;
            }
            ForeColor = foreColor;
            BackColor = backColor;
        }
    }
}
